package com.innjia.sys.service.impl;

import com.innjia.base.exception.MyException;
import com.innjia.base.utils.DateUtils;
import com.innjia.base.utils.MD5;
import com.innjia.base.utils.Utils;
import com.innjia.base.utils.ValidatorUtils;
import com.innjia.sys.dao.FileDao;
import com.innjia.sys.entity.FileEntity;
import com.innjia.sys.service.FileService;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.misc.BASE64Decoder;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;
import java.util.function.Supplier;

/**
 * 这里有一个隐患,没有限制文件大小,如果文件巨大,可能会出问题.暂时是没问题的,以后要改
 *
 * @author zhouxianglh@gmail.com
 */
@Service("fileService")
public class FileServiceImpl implements FileService {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileServiceImpl.class);

    private static final int MB_SIZE = 1024 * 1024;

    @Value("${file.fileStore}")
    private String fileRootPath;

    @Value("${file.maxFileSize}")
    private int maxFileSize = 15;

    @Autowired
    private FileDao fileDao;

    @Override
    public String saveFile(Date limitDate, byte[] bytes, String levelPath) {
        return saveFile(limitDate, bytes, () -> writeFile(levelPath, bytes));
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public String saveFileWithType(Date limitDate, byte[] bytes, String levelPath, String type) {
        return saveFile(limitDate, bytes, () -> writeFileWithType(levelPath, bytes, type));
    }

    @Override
    public byte[] getFile(String fileId) {
        FileEntity fileEntity = fileDao.queryObject(fileId);
        if (null == fileEntity) {
            throw new RuntimeException("文件不存在");
        }
        try {
            return readFile(fileEntity.getPath());
        } catch (IOException e) {
            LOGGER.error("", e);
            throw new RuntimeException(e);
        }
    }

    private byte[] readFile(String filePath) throws IOException {
        return FileUtils.readFileToByteArray(new File(filePath));
    }

    @Transactional(rollbackFor = {Exception.class})
    @Override
    public void deleteFile(String fileId) {
        FileEntity fileEntity = fileDao.queryObject(fileId);
        if (null == fileEntity) {
            throw new RuntimeException("文件不存在");
        }
        try {
            removeFile(fileEntity.getPath());
            fileDao.delete(fileId);
        } catch (IOException e) {
            LOGGER.error("", e);
            throw new RuntimeException(e);
        }
    }

    private void removeFile(String filePath) throws IOException {
        File file = new File(filePath);
        if (file.exists() && file.isFile()) {
            if (!file.delete()) {
                throw new RuntimeException("删除文件失败:" + filePath);
            }
        }
    }

    /*
     * 保存 base64 转码的文件(图片)
     */
    @Override
    public String saveBase64File(Date limitDate, String base64Str, String levelPath, String type) {
        return saveFileWithType(limitDate, base64Str2Byte(base64Str), levelPath, type);
    }

    /**
     * base64 编码字符串转 byte[]
     *
     * @param base64Str
     * @return byte[]
     */
    private byte[] base64Str2Byte(String base64Str) {
        if (null == base64Str) {
            return null;
        }
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            return decoder.decodeBuffer(base64Str);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
            throw new MyException("文件转码失败");
        }
    }

    /**
     * 写入文件
     *
     * @param levelPath
     *         子文件目录： 传参时使用 File.separator 拼接
     *         (ex:File.separator + "images" + File.separator + "house")
     * @param data
     * @param type
     * @return
     * @throws IOException
     */
    private String writeFileWithType(String levelPath, byte[] data, String type) {
        Date date = new Date();
        String filePath = fileRootPath;
        if (ValidatorUtils.isNotEmpty(levelPath)) {
            filePath += levelPath;
        }
        final String finalFilePath = filePath;
        return writeFile(data, () -> StringUtils.join(finalFilePath, File.separator,
                DateUtils.format(date, "yyyy"), File.separator, DateUtils.format(date, "MM"), File.separator, DateUtils.format(date, "dd"),
                File.separator, UUID.randomUUID() + "." + type));
    }

    @Transactional(rollbackFor = {Exception.class})
    private String saveFile(Date limitDate, byte[] bytes, Supplier<String> supplier) {
        if (bytes.length > (maxFileSize * MB_SIZE)) {
            throw new RuntimeException("文件大小超过 " + maxFileSize + " MB,不技持保存");
        }
        String md5 = MD5.md5Encode(bytes);
        String path = supplier.get();
        return save(path, limitDate, md5);
    }

    /**
     * 写入文件
     *
     * @param levelPath
     *         子文件目录： 传参时使用 File.separator 拼接
     *         (ex:File.separator + "images" + File.separator + "house")
     * @param data
     * @return
     * @throws IOException
     */
    private String writeFile(String levelPath, byte[] data) {
        Date date = new Date();
        String filePath = fileRootPath;
        if (ValidatorUtils.isNotEmpty(levelPath)) {
            filePath += levelPath;
        }
        final String finalFilePath = filePath;
        return writeFile(data, () -> StringUtils.join(finalFilePath, File.separator,
                DateUtils.format(date, "yyyy"), File.separator, DateUtils.format(date, "MM"), File.separator, DateUtils.format(date, "dd"),
                File.separator, UUID.randomUUID())
        );
    }

    private String save(String path, Date limitDate, String md5) {
        FileEntity fileEntity = new FileEntity();
        fileEntity.setId(Utils.uuid());
        fileEntity.setPath(path);
        fileEntity.setLimtdate(limitDate);
        fileEntity.setCreatedate(new Date());
        fileEntity.setMd5(md5);
        fileDao.save(fileEntity);
        return fileEntity.getId();
    }

    /**
     * 写入文件
     *
     * @param data
     * @param supplier
     * @return
     * @throws IOException
     */
    private String writeFile(byte[] data, Supplier<String> supplier) {
        try {
            String path = supplier.get();
            FileUtils.writeByteArrayToFile(new File(path), data);
            return path;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
